package comparation;

import java.util.ArrayList;
import java.util.HashMap;

import dataAccess.databaseManagement.entity.AssetEntity;
import dataAccess.databaseManagement.entity.PriceEntity;

public class MatchingAlgorithm3 {
	private int interval = 5;
	private double priceCoefficent = 0.4;
	private double volumeCoefficent = 0.6;
	private double maxAcceptableDistance = 0.3;
	
	private HashMap<AssetEntity, ArrayList<PriceEntity>> references;
	private ArrayList<PriceEntity> priceEntities; 
	
	public MatchingAlgorithm3() {
	}
	
	public MatchingAlgorithm3(int interval, double priceCoefficent, double volumeCoefficent, double maxAcceptableDistance, HashMap<AssetEntity, ArrayList<PriceEntity>> references, ArrayList<PriceEntity> priceEntities) {
		this.interval = interval;
		this.priceCoefficent = priceCoefficent;
		this.volumeCoefficent = volumeCoefficent;
		this.maxAcceptableDistance = maxAcceptableDistance;
		this.references = references;
		this.priceEntities = priceEntities;
	}
	
	public double predict(int i) {
		ArrayList<PriceEntity> matchingObject = new ArrayList<PriceEntity>();
		for (int j = i - interval; j < i; ++j) {
			matchingObject.add(priceEntities.get(j));
		}
		
		ArrayList<Double> volume = normalize(getVolume(matchingObject));
		ArrayList<Double> closePrices = normalize(getClosePrices(matchingObject));
		
		ArrayList<ArrayList<PriceEntity>> theBestMatchings = new ArrayList<ArrayList<PriceEntity>>();
		ArrayList<PriceEntity> theBestPredicts = new ArrayList<PriceEntity>();
		ArrayList<Double> mins = new ArrayList<Double>();
				
		for (int j = 0; j < i-interval-1; ++j) {
			ArrayList<PriceEntity> matchingResult = new ArrayList<PriceEntity>();
			for (int k = j; k < j+interval; ++k) {
				matchingResult.add(priceEntities.get(k));
			}
			
			ArrayList<Double> volumeMatching = normalize(getVolume(matchingResult));
			ArrayList<Double> closePricesMatching = normalize(getClosePrices(matchingResult));
			
			double distance = priceCoefficent*euclideanDistance(closePrices, closePricesMatching) + volumeCoefficent*euclideanDistance(volume, volumeMatching);
						
			if (distance <= maxAcceptableDistance) {
				mins.add(distance);
				theBestMatchings.add(matchingResult);
				theBestPredicts.add(priceEntities.get(j+interval));
			}
		}
		
		//references asset
		for (AssetEntity asset : references.keySet()) {
			ArrayList<PriceEntity> entityList = references.get(asset);
			
			int j = interval - 1;
			while (entityList.get(j).getDate().getTime() < priceEntities.get(i).getDate().getTime() && j < entityList.size()-1) {
				ArrayList<PriceEntity> matchingResult = new ArrayList<PriceEntity>();
				for (int k = j-interval+1; k <= j; ++k) {
					matchingResult.add(entityList.get(k));
				}
				
				ArrayList<Double> volumeMatching = normalize(getVolume(matchingResult));			
				ArrayList<Double> closePricesMatching = normalize(getClosePrices(matchingResult));
				
				double distance = priceCoefficent*euclideanDistance(closePrices, closePricesMatching) + volumeCoefficent*euclideanDistance(volume, volumeMatching);
				
				if (distance <= maxAcceptableDistance) {
					mins.add(distance);
					theBestMatchings.add(matchingResult);
					theBestPredicts.add(entityList.get(j+1));
				}
				
				++j;
			}
		}
		
		double rateOfChange = 0;

		for (int j = 0; j < mins.size(); ++j) {
			rateOfChange += theBestPredicts.get(j).getClose()/theBestMatchings.get(j).get(theBestMatchings.get(j).size()-1).getClose() - 1;
		}
		
		if (mins.size() == 0) 
			return 0;

		rateOfChange /= mins.size();
		return (priceEntities.get(i-1).getClose() + priceEntities.get(i-1).getClose()*rateOfChange);
	}

	private static ArrayList<Double> getVolume(
			ArrayList<PriceEntity> priceMatching) {
		ArrayList<Double> closePrices = new ArrayList<Double>();
		
		for (int i = 0; i < priceMatching.size(); ++i) {
			closePrices.add(priceMatching.get(i).getVolume());
		}
		
		return closePrices;
	}

	private static ArrayList<Double> getClosePrices(
			ArrayList<PriceEntity> priceList) {
		ArrayList<Double> closePrices = new ArrayList<Double>();
		
		for (int i = 0; i < priceList.size(); ++i) {
			closePrices.add(priceList.get(i).getClose());
		}
		
		return closePrices;
	}

	public static double euclideanDistance(ArrayList<Double> x1, ArrayList<Double> x2) {
		if (x1.size() != x2.size()) {
			return 0;
		}
		
		double result = 10e-5;
		
		for (int i = 0; i < x1.size(); ++i) {
			result += Math.pow(x1.get(i) - x2.get(i),2);
		}
		
		return Math.sqrt(result);
	}
	
	public static double mean (ArrayList<Double> x) {
		double mean = 0;
		for (Double e : x) {
			mean += e;
		}
		mean /= x.size();
		return mean;
	}
	
	public static double std (ArrayList<Double> x) {
		double variance = 10e-5;
		double mean = mean(x);
		for (Double e : x) {
			variance += (e - mean)*(e - mean);
		}
		variance /= x.size();
		return Math.sqrt(variance);
	}
	
	public static ArrayList<Double> normalize(ArrayList<Double> x) {
		double mean = mean(x);
		double std = std(x);
		
		for (int i = 0; i < x.size(); ++i) {
			x.set(i,(x.get(i) - mean)/std);
		}		
		
		return x;
	}

	
	public double getInterval() {
		return interval;
	}
	public void setInterval(int interval) {
		this.interval = interval;
	}
	public double getPriceCoefficent() {
		return priceCoefficent;
	}
	public void setPriceCoefficent(double priceCoefficent) {
		this.priceCoefficent = priceCoefficent;
	}
	public double getVolumeCoefficent() {
		return volumeCoefficent;
	}
	public void setVolumeCoefficent(double volumeCoefficent) {
		this.volumeCoefficent = volumeCoefficent;
	}
	public HashMap<AssetEntity, ArrayList<PriceEntity>> getReferences() {
		return references;
	}
	public void setReferences(HashMap<AssetEntity, ArrayList<PriceEntity>> references) {
		this.references = references;
	}
	public ArrayList<PriceEntity> getPriceEntities() {
		return priceEntities;
	}
	public void setPriceEntities(ArrayList<PriceEntity> priceEntities) {
		this.priceEntities = priceEntities;
	}
	public double getMaxAcceptableDistance() {
		return maxAcceptableDistance;
	}
	public void setMaxAcceptableDistance(double maxAcceptableDistance) {
		this.maxAcceptableDistance = maxAcceptableDistance;
	}
}
